#include "log.hpp"
#include "socket.hpp"
#include <iostream>
#include <algorithm>
#include <cstring>
#include <sys/select.h>
extern Log lg;

class selectServer
{
public:
    selectServer(uint16_t port)
        :_port(port) 
    {
        memset(_fdArray, -1, sizeof _fdArray);
    }
    bool init()
    {
        _listenSock.bind(8080);
        if(_listenSock.listen() == -1)
            return false;
        _fdArray[0] = _listenSock.fd();
        _listenSock.set_no_time_wait();
        return true;
    }
private:
    int setNeedFd(fd_set* prfds)
    {
        int maxFd = -1;
        for(int i = 0; i < fdNumMax; ++i)
            if(_fdArray[i] != defaultFd)
            {
                int fd = _fdArray[i];
                maxFd = std::max(maxFd, _fdArray[i]);
                FD_SET(fd, prfds);
            }
        return maxFd;
    }
public:
    bool start()
    {
        // 这里不能直接accept，因为这样还是阻塞等待
        int listenfd = _listenSock.fd();

        while(true)
        {
            // 我关心的是读事件是否就绪，所以要解决读事件就绪问题
            // 由于输入输出的原因，每次都要重新设置关心的fd集合
            fd_set rfds;
            FD_ZERO(&rfds);
            int maxFd = setNeedFd(&rfds);
            // FD_SET(listenfd, &rfds);
            struct timeval t = {1, 0};
            // 设置成nullptr表示非阻塞等待
            int n = select(maxFd + 1, &rfds, nullptr, nullptr, nullptr);
            switch(n)
            {
                case 0:   // 超时，没有事件
                    printf("time out, sec:%ld, usec:%ld\n", t.tv_sec, t.tv_usec);
                    break;
                case -1:   // 出现异常
                    std::cerr << "read fail" << std::endl;
                    break;
                default:
                    // 如果不处理，select将会一直提醒用户
                    eventDispatcher(rfds);
                    break;
                    
            }
        }
    }
private:
    void linkAccepter()
    {
        // 说明有新的链接到来，接收新连接，并标记比特位
        std::string srcIp; uint16_t srcPort;
        int sockFd = _listenSock.accept(&srcIp, &srcPort);
        if(sockFd == -1)
        {
            lg(Warning, "listenSock accept fail, %d, str: %s", errno, strerror(errno));
            return;
        }
        lg(Info, "get a new link, fd: %d", sockFd);
        // printFd();
        // 找到辅助数组中为空的位置，标记上去
        int i = 0;
        for(; i < fdNumMax; ++i)
            if(_fdArray[i] == defaultFd)
            {
                _fdArray[i] = sockFd;
                break;
            }
        if(i == fdNumMax)
            lg(Info, "server fd is full!");
    }
    // 处理读事件
    void readHandler(int pos)
    {
        int fd = _fdArray[pos];
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof buffer - 1);  // 这里存在问题，不一定一次都完整
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a message:" << buffer << std::endl;
        }
        else if(!n)
        {
            lg(Info, "client close the fd %d", fd);
            _fdArray[pos] = -1;
        }
        else
        {
            lg(Warning, "read fd %d fail!", fd);
            _fdArray[pos] = -1;
        }
    }
private:
    void eventDispatcher(fd_set& rfds)
    {
        for(int pos = 0; pos < fdNumMax; ++pos)
        {
            int sockFd = _fdArray[pos];
            if(FD_ISSET(sockFd, &rfds))
                if(sockFd == _listenSock.fd())  // 如果是listen套接字，那就就行链接管理
                    linkAccepter();
                else
                    readHandler(pos);
        }
    }
private:
    static const int defaultFd = -1;
    static const int fdNumMax = 1024;
private:
    tcp_socket _listenSock;
    uint16_t _port;
    // 由于要标记文件描述符，所以需要用一个辅助数组来存储各个位置的文件描述符
    int _fdArray[fdNumMax];

};